package ybl.wechat;

import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import ybl.wechat.entities.AccessToken;
import ybl.wechat.entities.QrCode;

import org.apache.commons.codec.digest.DigestUtils;
import org.json.JSONObject;
import org.json.XML;

import jakarta.servlet.http.HttpServletRequest;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Paths;
import java.nio.file.Files;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

public class WeChatService {
    private static final JSONObject menuConfig = ConfigSingleton.getMenuConfig();
    private static final WeChatConf WE_CHAT_CONF = ConfigSingleton.getInstance();
    private static final OkHttpClient client = new OkHttpClient();
    private static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json; charset=utf-8");

    /**
     * 获取AccessToken
     *
     * @return AccessToken对象
     * @throws IOException 如果在网络通信过程中发生IO错误
     */
    public static AccessToken getAccessToken() throws IOException {
        AccessToken accessToken = Storage.getAccessToken(WE_CHAT_CONF.getWECHAT().getAPP_ID());
        if (accessToken == null
                || accessToken.getUpdateAt().getTime() < System.currentTimeMillis() - 1000 * 60 * 60 * 2) {
            JSONObject jsonObject = getAccessTokenByRequest();
            String token = jsonObject.getString("access_token");
            Timestamp now = new Timestamp(System.currentTimeMillis());
            Storage.saveAndUpdateAccessToken(WE_CHAT_CONF.getWECHAT().getAPP_ID(), token, now);
            accessToken = new AccessToken(token, now);
        }
        return accessToken;
    }

    /**
     * 通过请求微信服务器获取访问令牌（AccessToken）。
     *
     * @return JSONObject 包含访问令牌
     * @throws IOException  如果在网络通信过程中发生IO错误
     * @throws ApiException 如果开放平台返回了错误代码
     */
    private static JSONObject getAccessTokenByRequest() throws IOException, ApiException {
        String url = String.format("%stoken?grant_type=client_credential&appid=%s&secret=%s",
                WE_CHAT_CONF.getWECHAT().getOPEN_API_URL_PREFIX(),
                WE_CHAT_CONF.getWECHAT().getAPP_ID(),
                WE_CHAT_CONF.getWECHAT().getAPP_SECRET());

        Request request = new Request.Builder().url(url).build();
        return WeChatUtils.executeRequest(request);
    }

    /**
     * 创建临时带参数的二维码
     *
     * @param sceneStr      场景值ID（字符串形式）
     * @param expireSeconds 二维码的有效时间，以秒为单位。最大不超过2592000（即30天）。
     * @return 二维码图片的URL。如果请求失败，则返回null。
     * @throws IOException 如果在网络通信过程中发生IO错误
     */
    public static String createTemporaryParametricQRCode(String sceneStr, int expireSeconds) throws IOException {
        // 逻辑保持不变，调用createQRCode方法
        return createQRCode("QR_STR_SCENE", sceneStr, expireSeconds);
    }

    /**
     * 创建永久带参数的二维码
     *
     * @param sceneStr 场景值ID（字符串形式），每个账号可创建数量有限。
     * @return 二维码图片的URL。如果请求失败，则返回null。
     * @throws IOException 如果在网络通信过程中发生IO错误
     */
    public static String createPermanentParametricQRCode(String sceneStr) throws IOException {
        // 逻辑保持不变，调用createQRCode方法
        return createQRCode("QR_LIMIT_STR_SCENE", sceneStr, 0);
    }

    /**
     * 生成二维码
     *
     * @param actionName    二维码类型（临时或永久）
     * @param sceneStr      场景值ID（字符串形式）
     * @param expireSeconds 二维码的有效时间，仅对临时二维码有效。
     * @return 二维码图片的URL。如果请求失败，则返回null。
     * @throws IOException 如果在网络通信过程中发生IO错误。
     */
    private static String createQRCode(String actionName, String sceneStr, int expireSeconds) throws IOException {
        String accessToken = getAccessToken().getAccessToken();
        String url = WE_CHAT_CONF.getWECHAT().getOPEN_API_URL_PREFIX() + "qrcode/create?access_token=" + accessToken;

        // 直接在jsonBody构造时包含action_info
        JSONObject jsonBody = new JSONObject()
                .put("action_name", actionName)
                .put("action_info", new JSONObject().put("scene", new JSONObject().put("scene_str", sceneStr)));

        // 对于临时二维码，添加expire_seconds参数
        if ("QR_STR_SCENE".equals(actionName) && expireSeconds > 0) {
            jsonBody.put("expire_seconds", expireSeconds);
        }

        RequestBody body = RequestBody.create(jsonBody.toString(), JSON_MEDIA_TYPE);
        Request request = new Request.Builder().url(url).post(body).build();

        JSONObject responseJson = WeChatUtils.executeRequest(request);
        // 通常，二维码的URL会在响应的"ticket"参数中，并需要进行相应的处理来获取最终的二维码图片URL
        if (responseJson.has("ticket")) {
            return "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + responseJson.getString("ticket");
        } else {
            throw new IOException("Failed to create QR code, response: " + responseJson.toString());
        }
    }

    public static String checkWxServer(Map<String, String> params) {
        // {signature=41e38f9ce047e488c3a91ad255bcd17f2c801d46,
        // echostr=7686252820168683519, timestamp=1710764009, nonce=1092866919}
        String signature = params.get("signature");
        String timestamp = params.get("timestamp");
        String nonce = params.get("nonce");

        // Initialize a list to store the token, timestamp, and nonce
        List<String> listTemp = new ArrayList<>();

        listTemp.add(WE_CHAT_CONF.getWECHAT().getTOKEN());
        listTemp.add(timestamp);
        listTemp.add(nonce);
        // Sort the list to ensure consistent string concatenation order
        Collections.sort(listTemp);

        // Use SHA1 algorithm to encrypt the sorted token, timestamp, and nonce, then
        // convert it into a hexadecimal string
        String hex = DigestUtils.sha1Hex(listTemp.get(0) + listTemp.get(1) + listTemp.get(2));

        // Compare the encrypted string with the provided signature; if they match, the
        // verification passes
        if (hex.equals(signature)) {
            return params.get("echostr");
        } else {
            return "";
        }
    }

    // 将xml转为json对象
    public static JSONObject requestBody2Json(HttpServletRequest request) throws IOException {
        StringBuilder sb = new StringBuilder();
        String line;

        BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }
        return XML.toJSONObject(sb.toString());
    }

    // 将json转为xml
    public static String json2Xml(JSONObject jsonObject) {
        return XML.toString(jsonObject);
    }

    // 构造text回复
    public static String buildTextReply(String toUserName, String fromUserName, String content) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("ToUserName", toUserName);
        jsonObject.put("FromUserName", fromUserName);
        jsonObject.put("CreateTime", System.currentTimeMillis() / 1000);
        jsonObject.put("MsgType", "text");
        jsonObject.put("Content", content);
        return XML.toString(new JSONObject().put("xml", jsonObject));
    }

    // 构造image回复
    public static String buildImageReply(String toUserName, String fromUserName, String mediaId) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("ToUserName", toUserName);
        jsonObject.put("FromUserName", fromUserName);
        jsonObject.put("CreateTime", System.currentTimeMillis() / 1000);
        jsonObject.put("MsgType", "image");
        jsonObject.put("Image", new JSONObject().put("MediaId", mediaId));
        return XML.toString(new JSONObject().put("xml", jsonObject));
    }

    /**
     * 发送客服消息
     * 
     * @param messageJson JSON对象，包含发送消息所需的所有参数和消息类型
     * @return JSONObject 响应的JSONObject
     * @throws IOException 如果在网络通信过程中发生IO错误
     */
    public static JSONObject sendCustomerServiceMessage(JSONObject messageJson) throws IOException, ApiException {
        String accessToken = getAccessToken().getAccessToken();
        String url = WE_CHAT_CONF.getWECHAT().getOPEN_API_URL_PREFIX() + "message/custom/send?access_token="
                + accessToken;

        RequestBody body = RequestBody.create(messageJson.toString(), MediaType.get("application/json; charset=utf-8"));
        Request request = new Request.Builder().url(url).post(body).build();

        return WeChatUtils.executeRequest(request);
    }

    /**
     * 新增临时素材
     *
     * @param filePath 素材文件的路径
     * @param type     素材的类型（image、voice、video、thumb）
     * @return JSONObject 响应的JSONObject，包含素材的media_id等信息
     * @throws IOException 如果在网络通信过程中发生IO错误
     */
    public static JSONObject addTemporaryMaterial(String filePath, String type) throws IOException, ApiException {
        String accessToken = getAccessToken().getAccessToken();
        String url = WE_CHAT_CONF.getWECHAT().getOPEN_API_URL_PREFIX() + "media/upload?access_token=" + accessToken
                + "&type=" + type;

        File file = new File(filePath);
        RequestBody fileBody = RequestBody.create(file, MediaType.get("application/octet-stream"));
        MultipartBody requestBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("media", file.getName(), fileBody)
                .build();

        Request request = new Request.Builder().url(url).post(requestBody).build();
        return WeChatUtils.executeRequest(request);
    }

    /**
     * 获取临时素材
     *
     * @param mediaId  素材的media_id
     * @param savePath 保存文件的路径（包含文件名）。如果素材是视频，此参数将用于保存包含视频URL的JSON文件。
     * @return boolean 操作成功返回true，否则返回false
     */
    public static boolean getTemporaryMaterial(String mediaId, String savePath) throws IOException {
        String accessToken = getAccessToken().getAccessToken();
        HttpUrl url = HttpUrl.parse(WE_CHAT_CONF.getWECHAT().getOPEN_API_URL_PREFIX() + "media/get")
                .newBuilder()
                .addQueryParameter("access_token", accessToken)
                .addQueryParameter("media_id", mediaId)
                .build();

        Request request = new Request.Builder().url(url.toString()).build();

        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("Unexpected code " + response);
            }

            MediaType mediaType = response.body().contentType();
            if (mediaType != null && mediaType.type().equals("text")) {
                // 处理视频文件，因为微信会返回一个JSON格式的响应包含视频URL
                Files.write(Paths.get(savePath), response.body().bytes());
            } else {
                // 处理其他类型的文件，直接保存到指定路径
                File file = new File(savePath);
                try (InputStream is = response.body().byteStream(); FileOutputStream fos = new FileOutputStream(file)) {
                    byte[] buffer = new byte[4096];
                    int len;
                    while ((len = is.read(buffer)) != -1) {
                        fos.write(buffer, 0, len);
                    }
                }
            }
            return true;
        }
    }

    /**
     * 添加永久素材
     *
     * @param filePath     素材文件的路径
     * @param type         素材的类型（image、voice、video、thumb）
     * @param title        素材的标题，仅对视频素材类型有效
     * @param introduction 素材的简介，仅对视频素材类型有效
     * @return JSONObject 响应的JSONObject，包含素材的信息
     * @throws IOException 如果在网络通信过程中发生IO错误
     */
    public static JSONObject addPermanentMaterial(String filePath, String type, String title, String introduction)
            throws IOException, ApiException {
        String accessToken = getAccessToken().getAccessToken();
        String url = WE_CHAT_CONF.getWECHAT().getOPEN_API_URL_PREFIX() + "material/add_material?access_token="
                + accessToken + "&type=" + type;

        File file = new File(filePath);
        RequestBody fileBody = RequestBody.create(file, MediaType.get("application/octet-stream"));

        MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM)
                .addFormDataPart("media", file.getName(), fileBody);

        // 如果是视频文件，需要额外提供title和introduction信息
        if ("video".equals(type)) {
            JSONObject description = new JSONObject()
                    .put("title", title)
                    .put("introduction", introduction);
            builder.addFormDataPart("description", description.toString());
        }

        RequestBody requestBody = builder.build();
        Request request = new Request.Builder().url(url).post(requestBody).build();

        return WeChatUtils.executeRequest(request);
    }

    /**
     * 获取永久素材
     *
     * @param mediaId 素材的media_id
     * @return JSONObject 响应的JSONObject，包含素材的详细信息
     * @throws IOException 如果在网络通信过程中发生IO错误
     */
    public static JSONObject getPermanentMaterial(String mediaId) throws IOException, ApiException {
        String accessToken = getAccessToken().getAccessToken();
        String url = WE_CHAT_CONF.getWECHAT().getOPEN_API_URL_PREFIX() + "material/get_material?access_token="
                + accessToken;

        JSONObject jsonBody = new JSONObject().put("media_id", mediaId);
        RequestBody body = RequestBody.create(jsonBody.toString(), MediaType.get("application/json; charset=utf-8"));
        Request request = new Request.Builder().url(url).post(body).build();

        return WeChatUtils.executeRequest(request);
    }

    /**
     * 创建和保存二维码
     * 
     * @throws IOException
     */
    public static void createAndSaveQRCodeForLogin() throws IOException {
        int expireSeconds = 300;
        String qrLink = createTemporaryParametricQRCode("scanThenLogin", expireSeconds);
        HttpUrl url = HttpUrl.parse(qrLink);
        String ticket = url.queryParameter("ticket");
        Storage.saveOrUpdateQrCode(ticket, expireSeconds, new Timestamp(System.currentTimeMillis()), false);
    }

    public static boolean updateQrCodeFlag(String ticket) throws IOException {
        Storage.deleteExpiredQrCodes();
        QrCode qrCode = Storage.getQrCodeInTTL(ticket);
        if (qrCode != null) {
            Storage.saveOrUpdateQrCode(ticket, qrCode.getExpire_seconds(), qrCode.getCreatedAt(), true);
            return true;
        }
        return false;
    }

    /**
     * 查询二维码状态
     * 1 删除所有不在有效期内的二维码
     * 2 查看 QRCODE 的有效期
     * 3 若不在有效期，则删除该二维码
     * 4 若在有效期，返回其状态
     * 
     * @param ticket
     * @return
     * @throws IOException
     */
    public static JSONObject getQrCodeFlag(String ticket) throws IOException {
        Storage.deleteExpiredQrCodes();
        JSONObject result = new JSONObject();
        QrCode qrCode = Storage.getQrCodeInTTL(ticket);
        if (qrCode != null) {
            result.put("flag", qrCode.isFlag());
            result.put("qeCode", qrCode);
        } else {
            result.put("flag", false);
            result.put("qeCode", "null");
        }
        return result;
    }

    // 直接转发消息到客服系统
    public static String transferToCustomerService(String toUserName, String fromUserName, long createTime) {
        JSONObject json = new JSONObject();
        json.put("ToUserName", toUserName);
        json.put("FromUserName", fromUserName);
        json.put("CreateTime", createTime);
        json.put("MsgType", "transfer_customer_service");
        return json2Xml(new JSONObject().put("xml", json));
    }

    // 转发消息到指定的客服账号
    public static String transferToSpecificCustomerService(String toUserName, String fromUserName, long createTime,
            String kfAccount) {
        JSONObject json = new JSONObject();
        json.put("ToUserName", toUserName);
        json.put("FromUserName", fromUserName);
        json.put("CreateTime", createTime);
        json.put("MsgType", "transfer_customer_service");

        JSONObject transInfo = new JSONObject();
        transInfo.put("KfAccount", kfAccount);
        json.put("TransInfo", transInfo);

        return json2Xml(new JSONObject().put("xml", json));
    }

    /**
     * 创建自定义菜单
     * 
     * @param menuJson JSON对象，描述了菜单的结构
     * @return JSONObject 响应的JSONObject，包含创建菜单的结果
     * @throws IOException 如果在网络通信过程中发生IO错误
     */
    public static JSONObject createMenu() throws IOException, ApiException {
        String accessToken = getAccessToken().getAccessToken();
        String url = WE_CHAT_CONF.getWECHAT().getOPEN_API_URL_PREFIX() + "menu/create?access_token=" + accessToken;

        RequestBody body = RequestBody.create(menuConfig.toString(), JSON_MEDIA_TYPE);
        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .build();

        return WeChatUtils.executeRequest(request);
    }
}
